<?php
/**
 * 图片上传处理类
 * 
 * 这个类专门处理文章图片的上传、验证和处理
 * 支持常见的图片格式，自动创建目录，压缩图片等功能
 * 
 * 使用方法：
 * $uploader = new ImageUploader();
 * $result = $uploader->upload($_FILES['image']);
 * if ($result['success']) {
 *     echo "上传成功，文件路径：" . $result['path'];
 * } else {
 *     echo "上传失败：" . $result['error'];
 * }
 */
class ImageUploader {
    
    /**
     * 上传目录
     * 图片会保存在这个目录下，按年月分文件夹存放
     */
    private $uploadDir = 'uploads/images/';
    
    /**
     * 允许的文件类型
     * 只允许常见的图片格式
     */
    private $allowedTypes = array('jpg', 'jpeg', 'png', 'gif', 'webp');
    
    /**
     * 最大文件大小（字节）
     * 默认5MB，可以根据需要调整
     */
    private $maxSize = 5 * 1024 * 1024; // 5MB
    
    /**
     * 图片最大宽度
     * 超过这个宽度会自动压缩
     */
    private $maxWidth = 800;
    
    /**
     * 图片最大高度
     * 超过这个高度会自动压缩
     */
    private $maxHeight = 600;
    
    /**
     * 构造函数
     * 在创建对象时自动检查并创建上传目录
     */
    public function __construct() {
        // 确保上传目录存在
        $this->createUploadDir();
    }
    
    /**
     * 创建上传目录
     * 
     * 这个方法会创建基础的uploads目录和按年月分类的子目录
     * 比如：uploads/images/2025/06/
     * 
     * @return void
     */
    private function createUploadDir() {
        // 创建基础目录
        if (!is_dir($this->uploadDir)) {
            mkdir($this->uploadDir, 0755, true);
        }
        
        // 创建按年月分类的目录
        $yearMonth = date('Y/m');
        $fullDir = $this->uploadDir . $yearMonth;
        if (!is_dir($fullDir)) {
            mkdir($fullDir, 0755, true);
        }
    }
    
    /**
     * 主要的上传方法
     * 
     * 这是外部调用的主要方法，处理整个上传流程
     * 包括验证、移动文件、压缩图片等步骤
     * 
     * @param array $file $_FILES中的文件信息
     * @return array 返回上传结果，包含success、path、error等信息
     */
    public function upload($file) {
        // 首先验证文件是否有效
        $validation = $this->validateFile($file);
        if (!$validation['valid']) {
            return array(
                'success' => false,
                'error' => $validation['error']
            );
        }
        
        // 生成唯一的文件名，避免重复
        $fileName = $this->generateFileName($file['name']);
        $yearMonth = date('Y/m');
        $relativePath = $this->uploadDir . $yearMonth . '/' . $fileName;
        $fullPath = $relativePath;
        
        // 移动上传的文件到目标位置
        if (move_uploaded_file($file['tmp_name'], $fullPath)) {
            // 上传成功后，压缩图片（如果需要）
            $this->resizeImage($fullPath);
            
            return array(
                'success' => true,
                'path' => $relativePath,
                'filename' => $fileName
            );
        } else {
            return array(
                'success' => false,
                'error' => '文件移动失败，请检查目录权限'
            );
        }
    }
    
    /**
     * 验证上传的文件
     * 
     * 检查文件大小、类型、是否有错误等
     * 这一步很重要，确保只接受合法的图片文件
     * 
     * @param array $file $_FILES中的文件信息
     * @return array 验证结果
     */
    private function validateFile($file) {
        // 检查是否有文件上传
        if (!isset($file['tmp_name']) || empty($file['tmp_name'])) {
            return array('valid' => false, 'error' => '请选择要上传的图片');
        }
        
        // 检查上传过程中是否有错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return array('valid' => false, 'error' => '文件上传出错：' . $this->getUploadError($file['error']));
        }
        
        // 检查文件大小
        if ($file['size'] > $this->maxSize) {
            $maxSizeMB = round($this->maxSize / 1024 / 1024, 1);
            return array('valid' => false, 'error' => "文件太大，最大允许 {$maxSizeMB}MB");
        }
        
        // 检查文件类型
        $fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($fileExt, $this->allowedTypes)) {
            $allowedStr = implode('、', $this->allowedTypes);
            return array('valid' => false, 'error' => "不支持的文件类型，只允许：{$allowedStr}");
        }
        
        // 验证是否为真实的图片文件（防止伪造扩展名）
        $imageInfo = getimagesize($file['tmp_name']);
        if (!$imageInfo) {
            return array('valid' => false, 'error' => '文件不是有效的图片');
        }
        
        return array('valid' => true);
    }
    
    /**
     * 生成唯一的文件名
     * 
     * 为了避免文件名冲突，我们生成一个基于时间戳和随机数的文件名
     * 同时保留原始的文件扩展名
     * 
     * @param string $originalName 原始文件名
     * @return string 新的唯一文件名
     */
    private function generateFileName($originalName) {
        $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        $timestamp = time();
        $random = mt_rand(1000, 9999);
        return "img_{$timestamp}_{$random}.{$ext}";
    }
    
    /**
     * 压缩图片
     * 
     * 如果图片尺寸太大，自动压缩到合适的大小
     * 这样可以节省存储空间，提高网页加载速度
     * 
     * @param string $imagePath 图片文件路径
     * @return void
     */
    private function resizeImage($imagePath) {
        $imageInfo = getimagesize($imagePath);
        if (!$imageInfo) return;
        
        list($width, $height) = $imageInfo;
        
        // 如果图片尺寸在允许范围内，就不需要压缩
        if ($width <= $this->maxWidth && $height <= $this->maxHeight) {
            return;
        }
        
        // 计算压缩后的新尺寸，保持宽高比例
        $ratio = min($this->maxWidth / $width, $this->maxHeight / $height);
        $newWidth = (int)($width * $ratio);
        $newHeight = (int)($height * $ratio);
        
        // 根据图片类型创建图片资源
        $sourceImage = $this->createImageFromFile($imagePath, $imageInfo[2]);
        if (!$sourceImage) return;
        
        // 创建新的图片画布
        $newImage = imagecreatetruecolor($newWidth, $newHeight);
        
        // 保持PNG和GIF的透明背景
        if ($imageInfo[2] == IMAGETYPE_PNG || $imageInfo[2] == IMAGETYPE_GIF) {
            // 设置透明背景
            imagealphablending($newImage, false);
            imagesavealpha($newImage, true);
            $transparent = imagecolorallocatealpha($newImage, 255, 255, 255, 127);
            imagefill($newImage, 0, 0, $transparent);
        }
        
        // 重新采样图片，进行压缩
        imagecopyresampled($newImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
        
        // 保存压缩后的图片，覆盖原文件
        $this->saveImage($newImage, $imagePath, $imageInfo[2]);
        
        // 释放内存资源
        imagedestroy($sourceImage);
        imagedestroy($newImage);
    }
    
    /**
     * 根据文件路径和图片类型创建图片资源
     * 
     * 这个私有方法根据不同的图片类型创建对应的图片资源
     * 支持JPEG、PNG、GIF、WEBP等常见格式
     * 
     * @param string $filePath 图片文件的完整路径
     * @param int $imageType 图片类型常量（IMAGETYPE_JPEG等）
     * @return resource|false 图片资源或false（失败时）
     */
    private function createImageFromFile($filePath, $imageType) {
        // 根据图片类型使用不同的函数创建图片资源
        switch ($imageType) {
            case IMAGETYPE_JPEG:
                // 创建JPEG图片资源
                return imagecreatefromjpeg($filePath);
            case IMAGETYPE_PNG:
                // 创建PNG图片资源
                return imagecreatefrompng($filePath);
            case IMAGETYPE_GIF:
                // 创建GIF图片资源
                return imagecreatefromgif($filePath);
            case IMAGETYPE_WEBP:
                // 创建WebP图片资源（需要PHP 5.5+）
                if (function_exists('imagecreatefromwebp')) {
                    return imagecreatefromwebp($filePath);
                }
                break;
        }
        
        // 不支持的图片类型，返回false
        return false;
    }
    
    /**
     * 保存图片资源到文件
     * 
     * 这个私有方法将图片资源保存为指定格式的文件
     * 根据原始图片类型选择相应的保存函数
     * 
     * @param resource $image 要保存的图片资源
     * @param string $filePath 保存的文件路径
     * @param int $imageType 图片类型常量
     * @return bool 保存是否成功
     */
    private function saveImage($image, $filePath, $imageType) {
        // 根据图片类型使用不同的函数保存图片
        switch ($imageType) {
            case IMAGETYPE_JPEG:
                // 保存为JPEG格式，质量设为85（0-100）
                return imagejpeg($image, $filePath, 85);
            case IMAGETYPE_PNG:
                // 保存为PNG格式，压缩级别设为6（0-9）
                return imagepng($image, $filePath, 6);
            case IMAGETYPE_GIF:
                // 保存为GIF格式
                return imagegif($image, $filePath);
            case IMAGETYPE_WEBP:
                // 保存为WebP格式（需要PHP 5.5+）
                if (function_exists('imagewebp')) {
                    return imagewebp($image, $filePath, 85);
                }
                break;
        }
        
        // 不支持的图片类型，返回false
        return false;
    }
    
    /**
     * 获取上传错误的详细描述
     * 
     * 这个私有方法将PHP的上传错误代码转换为用户友好的错误消息
     * 帮助用户理解上传失败的具体原因
     * 
     * @param int $errorCode PHP上传错误代码
     * @return string 用户友好的错误描述
     */
    private function getUploadError($errorCode) {
        // 根据错误代码返回相应的中文错误描述
        switch ($errorCode) {
            case UPLOAD_ERR_INI_SIZE:
                return '文件大小超过了服务器限制';
            case UPLOAD_ERR_FORM_SIZE:
                return '文件大小超过了表单限制';
            case UPLOAD_ERR_PARTIAL:
                return '文件只上传了一部分';
            case UPLOAD_ERR_NO_FILE:
                return '没有文件被上传';
            case UPLOAD_ERR_NO_TMP_DIR:
                return '服务器临时目录不存在';
            case UPLOAD_ERR_CANT_WRITE:
                return '文件写入失败，请检查权限';
            case UPLOAD_ERR_EXTENSION:
                return '文件上传被扩展阻止';
            default:
                return '未知上传错误';
        }
    }
    
    /**
     * 删除已上传的图片文件
     * 
     * 这个公共方法用于删除不需要的图片文件
     * 在删除文章或更换图片时使用
     * 
     * @param string $imagePath 要删除的图片相对路径
     * @return bool 删除是否成功
     * 
     * 使用示例：
     * $uploader = new ImageUploader();
     * $success = $uploader->deleteImage('uploads/images/2025/06/img_123456_7890.jpg');
     * if ($success) {
     *     echo '图片删除成功';
     * }
     */
    public function deleteImage($imagePath) {
        // 检查文件是否存在
        if (file_exists($imagePath)) {
            // 删除文件并返回结果
            return unlink($imagePath);
        }
        
        // 文件不存在，返回false
        return false;
    }
}

/**
 * 全局图片上传函数（兼容性函数）
 * 
 * 这个函数提供了一个简单的全局接口来上传图片
 * 内部使用ImageUploader类处理上传逻辑
 * 
 * @param array $file $_FILES数组中的文件信息
 * @return array 上传结果数组，包含success、path、error等信息
 * 
 * 使用示例：
 * $result = uploadImage($_FILES['image']);
 * if ($result['success']) {
 *     echo '上传成功：' . $result['path'];
 * } else {
 *     echo '上传失败：' . $result['error'];
 * }
 */
function uploadImage($file) {
    // 创建ImageUploader实例并调用upload方法
    $uploader = new ImageUploader();
    return $uploader->upload($file);
}

/**
 * 全局图片删除函数（兼容性函数）
 * 
 * 这个函数提供了一个简单的全局接口来删除图片
 * 内部使用ImageUploader类处理删除逻辑
 * 
 * @param string $imagePath 要删除的图片路径
 * @return bool 删除是否成功
 * 
 * 使用示例：
 * if (deleteImage('uploads/images/old_image.jpg')) {
 *     echo '图片删除成功';
 * }
 */
function deleteImage($imagePath) {
    // 创建ImageUploader实例并调用deleteImage方法
    $uploader = new ImageUploader();
    return $uploader->deleteImage($imagePath);
}
?> 